package com.fund.order.service.Impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fund.api.client.*;
import com.fund.api.entity.AvailableShare;
import com.fund.api.entity.BankCard;
import com.fund.api.entity.ClientVO;
import com.fund.api.entity.ProductVO;
import com.fund.common.constant.StatusCodeConstant;
import com.fund.common.constant.SystemConstant;
import com.fund.common.enums.ClientStatus;
import com.fund.common.enums.OrderStatus;
import com.fund.common.enums.SystemStatus;
import com.fund.common.enums.TradeType;
import com.fund.common.pojo.Result;
import com.fund.common.utils.UserContext;
import com.fund.order.mapper.OrderMapper;
import com.fund.order.pojo.dto.OrderDTO;
import com.fund.api.entity.TradeOrder;
import com.fund.order.pojo.vo.OrderVO;
import com.fund.order.service.OrderService;
import com.fund.order.utils.OrderNumberGenerator;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, TradeOrder> implements OrderService {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    BankCardClient bankCardClient;

    @Autowired
    ClientsClient clientsClient;

    @Autowired
    ProductClient productClient;

    @Autowired
    AvailableShareClient availableShareClient;

    @Autowired
    ShareHoldingClient shareHoldingClient;

    private static final Integer BATCH_SIZE = 1000;

    /**
     * 购买基金
     * @param orderDTO
     * @return
     */
    @Override
    public Result<OrderVO> purchaseFund(OrderDTO orderDTO) {
        //判断是否在柜台开启期间
        String status = stringRedisTemplate.opsForValue().get(SystemConstant.SYSTEM_BUSINESS_STATUS_KEY);
        if (status == null || status.equals(SystemStatus.CLOSED.getValue().toString())) {
            return Result.fail("已打烊");
        }
        //判断余额是否充足
        Result<BankCard> result = bankCardClient.getBankCardById(orderDTO.getCardId());
        BigDecimal balance = result.getData().getBalance();
        if(balance.compareTo(orderDTO.getAmount())<0){
            return Result.fail("余额不足");
        }

        //判断客户状态是否冻结
        Result<ClientVO> clientVOResult = clientsClient.findById(orderDTO.getClientId());
        ClientVO clientVO = clientVOResult.getData();
        //客户是否存在
        if(clientVO == null || clientVO.getId() == null){
            return Result.fail("该客户不存在");
        }
        ClientStatus clientStatus = clientVOResult.getData().getStatus();
        if(clientStatus.getValue().equals(ClientStatus.FROZEN.getValue())){
            return Result.fail("该客户账号当前被冻结");
        }
        //查询判断基金是否存在
        Result<ProductVO> productVOResult = productClient.getById(orderDTO.getFundId());
        ProductVO productVO = productVOResult.getData();
        if(productVO==null || productVO.getId() == null){
            return Result.fail("该基金不存在");
        }

        //创建订单
        TradeOrder order = new TradeOrder();
        BeanUtils.copyProperties(orderDTO, order);

        //填充参数
        order.setCreateTime(LocalDateTime.now());
        order.setUpdateTime(LocalDateTime.now());
        order.setOperatorId(UserContext.getUser());
        order.setOrderStatus(OrderStatus.PROCESSING);
        order.setClientAccount(clientVOResult.getData().getAccountNumber());
        order.setType(TradeType.PURCHASE);

        //获取当前工作日
        String workDayString = stringRedisTemplate.opsForValue().get(SystemConstant.WORK_DAY_KEY);
        if(workDayString == null){
            return Result.fail("工作日信息不可用");
        }
        int workDay = Integer.parseInt(workDayString);
        //设置工作日
        if(LocalTime.now().isAfter(SystemConstant.TRADE_END_TIME)){
            workDay+=1;
        }
        order.setTradeDay(workDay);
        order.setFundCode(productVOResult.getData().getCode());
        //生成订单号
        String newOrderNumber = OrderNumberGenerator.getNewOrderNumber(TradeType.PURCHASE);
        order.setOrderNumber(newOrderNumber);

        //写入数据库
        save(order);

        //构建vo返回
        OrderVO orderVO = new OrderVO();
        BeanUtils.copyProperties(order, orderVO);
        return Result.success(orderVO);
    }

    /**
     * 赎回基金
     *
     * @param orderDTO
     * @return
     */
    @GlobalTransactional
    @Override
    public Result<List<OrderVO>> redemptionFund(OrderDTO orderDTO) {
        //判断是否在系统工作时间
        String systemStatus = stringRedisTemplate.opsForValue().get(SystemConstant.SYSTEM_BUSINESS_STATUS_KEY);
        if(systemStatus == null || systemStatus.equals(SystemStatus.CLOSED.getValue().toString())){
            return Result.fail("不在系统营业时间内");
        }
        //查询可用份额数（返回该客户对该基金所有可用的份额表数据）
        Result<List<AvailableShare>> shareResult = availableShareClient.
                getAvailableShare(orderDTO.getClientId(),orderDTO.getFundId());
        List<AvailableShare> shareList = shareResult.getData();
        if(shareList == null || shareList.isEmpty()){
            return Result.fail("可用份额不足");
        }
        //确认基金存在，同时取出基金code
        Result<ProductVO> productVOResult = productClient.getById(orderDTO.getFundId());
        ProductVO productVO = productVOResult.getData();
        if(productVO==null || productVO.getId() == null){
            return Result.fail("该产品不存在或已被删除");
        }
        String productCode = productVO.getCode();

        //确认客户存在，同时取出客户账号
        Result<ClientVO> clientResult = clientsClient.findById(orderDTO.getClientId());
        ClientVO clientVO = clientResult.getData();
        if(clientVO == null || clientVO.getId() == null){
            return Result.fail("该客户不存在或已注销");
        }
        String accountNumber = clientVO.getAccountNumber();

        //判断赎回的份额是否大于可用份额
        int total = 0;
        for(AvailableShare share : shareList){
            total+=share.getShareTotal();
        }
        if(orderDTO.getShareQuantity() > total){
            return Result.fail("可用份额不足");
        }

        //直到赎回份额归零为止，遍历每个表数据创建订单
        List<TradeOrder> orders = new ArrayList<>();
        int redemptionShare = orderDTO.getShareQuantity();
        for(AvailableShare share : shareList){
            //当赎回份额数大于该银行卡持有的可用份额
            //那么直接将所有份额用于申请赎回，并把该银行卡的可用份额归零
            int orderShare = 0;
            if(redemptionShare > share.getShareTotal()){
                redemptionShare-=share.getShareTotal();
                share.setShareTotal(0);
                orderShare = share.getShareTotal();
            }
            //当赎回份额小于或等于该银行卡持有的可用份额，那么当前银行卡减去赎回份额
            else {
                share.setShareTotal(share.getShareTotal()-redemptionShare);
                orderShare = redemptionShare;
                redemptionShare = 0;
            }
            TradeOrder order = createRedemptionOrder(share, orderShare, productCode, accountNumber);
            if(order == null){
                return Result.fail("系统工作日不可用");
            }
            orders.add(order);
            if(redemptionShare == 0) break;
        }
        //注：从此处开始有批量增改，注意事务
        //将修改后的可用份额传回数据库
        Result<Boolean> result = availableShareClient.modifyAvailableShares(shareList);
        if(!result.getData()){
            throw new RuntimeException("可用份额修改失败");
        }
        //保存订单
        saveBatch(orders);

        //返回订单vo
        List<OrderVO> orderVOList = new ArrayList<>();
        for(TradeOrder order : orders){
            OrderVO orderVO = new OrderVO();
            BeanUtils.copyProperties(order, orderVO);
            orderVOList.add(orderVO);
        }

        return Result.success(orderVOList);
    }

    /**
     * 创建赎回订单
     * @param share 可支配份额
     * @param orderShare 该订单的赎回份额
     * @param fundCode 基金代码，冗余字段填充
     * @param accountNumber 客户账号，冗余字段填充
     * @return order 赎回订单
     */
    private TradeOrder createRedemptionOrder(AvailableShare share,Integer orderShare,
                                             String fundCode,String accountNumber){
        TradeOrder order = new TradeOrder();
        //设定交易工作日：判断是否过三点，过三点则下一日
        String workdayString = stringRedisTemplate.opsForValue().get(SystemConstant.WORK_DAY_KEY);
        if(workdayString == null){
            return null;
        }
        int workDay = Integer.parseInt(workdayString);
        if(LocalTime.now().isAfter(SystemConstant.TRADE_END_TIME)){
            workDay += 1;
        }
        order.setTradeDay(workDay);
        //复制客户、银行卡、基金id，并重新设置份额
        BeanUtils.copyProperties(share, order);
        order.setShareQuantity(orderShare);

        order.setCreateTime(LocalDateTime.now());
        order.setUpdateTime(LocalDateTime.now());
        order.setOperatorId(UserContext.getUser());
        order.setOrderStatus(OrderStatus.PROCESSING);
        order.setType(TradeType.REDEMPTION);
        String newOrderNumber = OrderNumberGenerator.getNewOrderNumber(TradeType.REDEMPTION);
        order.setOrderNumber(newOrderNumber);
        order.setFundCode(fundCode);
        order.setClientAccount(accountNumber);
        return order;
    }

    /**
     * 确认所有订单
     * @return
     */
    @Override
    public Result<Boolean> confirmAllOrders() {
        //查询所有待确认状态的申购订单
        return null;
    }

    /**
     * 处理所有赎回订单
     * @return
     */
    @Override
    public Result<Boolean> dealAllRedemptionOrders() {
        //赎回涉及业务：
        //1. 持有份额扣减。可支配份额无需扣减。
        //2. 钱转入银行卡
        //3. 对应的流水和份额变化
        //查询所有处于待处理状态的赎回订单
        //分批处理赎回订单，记录上次处理到的id
        long lastId = 0L;
        while(true){
            //每次处理1000条
            Page<TradeOrder> orderPage = lambdaQuery()
                    .eq(TradeOrder::getOrderStatus, OrderStatus.PROCESSING)
                    .eq(TradeOrder::getType, TradeType.REDEMPTION)
                    .page(new Page<>(1, BATCH_SIZE));
            List<TradeOrder> orderList = orderPage.getRecords();
            //为空则说明已经处理完成
            if(orderList == null || orderList.isEmpty()){
                break;
            }
            //统计所有订单需要用到的产品id
            HashSet<Long> idSet = new HashSet<>();
            for(TradeOrder order : orderList){
                idSet.add(order.getId());
            }
            List<Long> idList = new ArrayList<>(idSet);
            //查询产品的最新净值
            Result<HashMap<Long, BigDecimal>> netWorthResult = productClient.getNetWorthByIds(idList);
            if(!netWorthResult.getCode().equals(StatusCodeConstant.SUCCESS_CODE)){
                throw new RuntimeException(netWorthResult.getMsg());
            }
            HashMap<Long, BigDecimal> netWorthMap = netWorthResult.getData();
            String workdayString = stringRedisTemplate.opsForValue().get(SystemConstant.WORK_DAY_KEY);
            if(workdayString == null){
                throw new RuntimeException("工作日信息不可用");
            }
            Integer workday = Integer.valueOf(workdayString);
            // 对订单进行更新
            orderList.forEach(order -> {
                //计算更新金额
                BigDecimal netWorth = netWorthMap.get(order.getFundId());
                if(netWorth == null){
                    throw new RuntimeException("净值不可用，fundId:"+order.getFundId());
                }
                BigDecimal amount = netWorth.multiply(new BigDecimal(order.getShareQuantity()));
                order.setAmount(amount);
                //更新其他字段
                order.setConfirmWorth(netWorth);
                order.setUpdateTime(LocalDateTime.now());
                order.setOperatorId(1L);
                order.setOrderStatus(OrderStatus.COMPLETED);
                //获取当前工作日并更新
                order.setConfirmDay(workday);
            });

            //TODO 改为mq
            //修改持有份额
            Result<Boolean> shareHoldingResult = shareHoldingClient.updateShareHoldings(orderList);
            if(!shareHoldingResult.getCode().equals(StatusCodeConstant.SUCCESS_CODE)){
                throw new RuntimeException(shareHoldingResult.getMsg());
            }
            //TODO 这里还没写完（清算业务的一部分）
            //转钱
            bankCardClient.updateBalanceByOrders(orderList);
        }

        //
        return null;
    }

    @Override
    public Result<Boolean> dealAllPerchaseOrders() {
        // 批处理每次处理1000条数据
        while(true){
            //每次处理1000条
            Page<TradeOrder> orderPage = lambdaQuery()
                    .eq(TradeOrder::getOrderStatus, OrderStatus.PROCESSING)
                    .eq(TradeOrder::getType, TradeType.REDEMPTION)
                    .page(new Page<>(1, BATCH_SIZE));
            List<TradeOrder> orderList = orderPage.getRecords();
            //为空则说明已经处理完成
            if(orderList == null || orderList.isEmpty()){
                break;
            }
            //统计所有订单需要用到的产品id
            HashSet<Long> idSet = new HashSet<>();
            for(TradeOrder order : orderList){
                idSet.add(order.getId());
            }
            List<Long> idList = new ArrayList<>(idSet);
            //查询产品的最新净值
            Result<HashMap<Long, BigDecimal>> netWorthResult = productClient.getNetWorthByIds(idList);
            if(!netWorthResult.getCode().equals(StatusCodeConstant.SUCCESS_CODE)){
                throw new RuntimeException(netWorthResult.getMsg());
            }
            HashMap<Long, BigDecimal> netWorthMap = netWorthResult.getData();
            String workdayString = stringRedisTemplate.opsForValue().get(SystemConstant.WORK_DAY_KEY);
            if(workdayString == null){
                throw new RuntimeException("工作日信息不可用");
            }
            Integer workday = Integer.valueOf(workdayString);
            // 处理订单状态为待处理的订单
            // 相对应用户的银行卡中扣除份额

            // 处理订单状态为待确认的订单
            // 相对应的用户的份额表发生更新
        }
        return null;
    }
}
